package com.J.Plane;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

public class Matrix44 {
	public float n00;
	public float n01;
	public float n02;
	public float n03;

	public float n10;
	public float n11;
	public float n12;
	public float n13;

	public float n20;
	public float n21;
	public float n22;
	public float n23;

	public float n30;
	public float n31;
	public float n32;
	public float n33;

	private FloatBuffer mFloatBuffer;
	private boolean mNeedUpdateFloatBuffer;

	public Matrix44() {
		this.identity();
		mNeedUpdateFloatBuffer = true;
	}

	public void identity() {
		n01 = n02 = n03 = n10 = n12 = n13 = n20 = n21 = n23 = n30 = n31 = n32 = 0;

		n00 = n11 = n22 = n33 = 1;

		needUpdate();
	}

	private void needUpdate() {
		mNeedUpdateFloatBuffer = true;
	}

	/**
	 * Make transformation matrix
	 * 
	 * @param position
	 * @param scale
	 * @param orientation
	 */
	public void makeTransform(final Vector3 position, final Vector3 scale,
			final Quaternion orientation) {
		// ...
		// Do something...
		//
		identity();
		setTrans(position);

		Matrix44 matScale = new Matrix44();
		matScale.n00 = scale.x;
		matScale.n11 = scale.y;
		matScale.n22 = scale.z;

		Matrix44 matRot = new Matrix44();
		orientation.toMatrix(matRot);

		Matrix44.multiply(matRot, matScale, this);
		this.setTrans(position);

		n30 = n31 = n32 = 0;
		n33 = 1;

		needUpdate();
	}

	/**
	 * Return float buffer. If buffer is not ready to return, build float buffer
	 * up .
	 * 
	 * @return
	 */
	public FloatBuffer getFloatBuffer() {
		if (mFloatBuffer == null) {
			ByteBuffer bb = ByteBuffer.allocateDirect(16 * 4);
			bb.order(ByteOrder.nativeOrder());
			mFloatBuffer = bb.asFloatBuffer();
		}

		if (mNeedUpdateFloatBuffer) {
			mNeedUpdateFloatBuffer = false;

			// Revert current position.
			mFloatBuffer.position(0);

			//
			// Build float buffer with column first.
			// 1st 2nd 3rd 4th
			// 00 01 02 03 |
			// 10 11 12 13 |
			// 20 21 22 23 |
			// 30 31 32 33 |
			// \ /
			// v
			//
			mFloatBuffer.put(n00);
			mFloatBuffer.put(n10);
			mFloatBuffer.put(n20);
			mFloatBuffer.put(n30);

			mFloatBuffer.put(n01);
			mFloatBuffer.put(n11);
			mFloatBuffer.put(n21);
			mFloatBuffer.put(n31);

			mFloatBuffer.put(n02);
			mFloatBuffer.put(n12);
			mFloatBuffer.put(n22);
			mFloatBuffer.put(n32);

			mFloatBuffer.put(n03);
			mFloatBuffer.put(n13);
			mFloatBuffer.put(n23);
			mFloatBuffer.put(n33);

			mFloatBuffer.position(0);
		}

		return mFloatBuffer;
	}

	public void setTrans(final Vector3 v) {
		n03 = v.x;
		n13 = v.y;
		n23 = v.z;

		needUpdate();
	}

	/**
	 * Matrix multiplication. out = a x b
	 * Matrix a or b can be parameter out.
	 * i.e., multiply(a,b,a) is possible.
	 * or multiply(a,a,a) is ok.
	 * That's why I use local variables.
	 * 
	 * @param [in]a Left side matrix
	 * @param [in]b Right side matrix
	 * @param [out]out Output matrix
	 */
	public static void multiply(final Matrix44 a, final Matrix44 b,
			final Matrix44 out) {
		float out00 = a.n00 * b.n00 + a.n01 * b.n10 + a.n02 * b.n20 + a.n03 * b.n30; 
		float out01 = a.n00 * b.n01 + a.n01 * b.n11 + a.n02 * b.n21 + a.n03 * b.n31;
		float out02 = a.n00 * b.n02 + a.n01 * b.n12 + a.n02 * b.n22 + a.n03 * b.n32;
		float out03 = a.n00 * b.n03 + a.n01 * b.n13 + a.n02 * b.n23 + a.n03 * b.n33;
		
		float out10 = a.n10 * b.n00 + a.n11 * b.n10 + a.n12 * b.n20 + a.n13 * b.n30; 
		float out11 = a.n10 * b.n01 + a.n11 * b.n11 + a.n12 * b.n21 + a.n13 * b.n31;
		float out12 = a.n10 * b.n02 + a.n11 * b.n12 + a.n12 * b.n22 + a.n13 * b.n32;
		float out13 = a.n10 * b.n03 + a.n11 * b.n13 + a.n12 * b.n23 + a.n13 * b.n33;
		
		float out20 = a.n20 * b.n00 + a.n21 * b.n10 + a.n22 * b.n20 + a.n23 * b.n30; 
		float out21 = a.n20 * b.n01 + a.n21 * b.n11 + a.n22 * b.n21 + a.n23 * b.n31;
		float out22 = a.n20 * b.n02 + a.n21 * b.n12 + a.n22 * b.n22 + a.n23 * b.n32;
		float out23 = a.n20 * b.n03 + a.n21 * b.n13 + a.n22 * b.n23 + a.n23 * b.n33;
		
		float out30 = a.n30 * b.n00 + a.n31 * b.n10 + a.n32 * b.n20 + a.n33 * b.n30; 
		float out31 = a.n30 * b.n01 + a.n31 * b.n11 + a.n32 * b.n21 + a.n33 * b.n31;
		float out32 = a.n30 * b.n02 + a.n31 * b.n12 + a.n32 * b.n22 + a.n33 * b.n32;
		float out33 = a.n30 * b.n03 + a.n31 * b.n13 + a.n32 * b.n23 + a.n33 * b.n33;
		
		out.n00 = out00;
		out.n01 = out01;
		out.n02 = out02;
		out.n03 = out03;
		
		out.n10 = out10;
		out.n11 = out11;
		out.n12 = out12;
		out.n13 = out13;
		
		out.n20 = out20;
		out.n21 = out21;
		out.n22 = out22;
		out.n23 = out23;
		
		out.n30 = out30;
		out.n31 = out31;
		out.n32 = out32;
		out.n33 = out33;
		
	}
	
	public static float DEG2RAD(float angle) {
		return angle*(float)Math.PI/180.0f;
	}
	public static float RAD2DEG(float radian) {
		return radian*360.0f/(float)Math.PI;
	}
}
